package com.wans.guli.product.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wans.guli.common.utils.R;
import com.wans.guli.product.entity.CategoryBrandRelationEntity;
import com.wans.guli.product.service.CategoryBrandRelationService;
import com.wans.guli.product.vo.CategoryVO;
import com.wans.guli.product.vo.web.CategoryThreeLevelVo;
import com.wans.guli.product.vo.web.CategoryTwoLevelVo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wans.guli.common.utils.PageUtils;
import com.wans.guli.common.utils.Query;

import com.wans.guli.product.dao.CategoryDao;
import com.wans.guli.product.entity.CategoryEntity;
import com.wans.guli.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ObjectMapper jsonMapper;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 查询所有分类
     *
     * @return 结构树形结构
     */
    @Override
    public List<CategoryVO> listTree() {

        // 查询所有分类
        List<CategoryEntity> list = this.list();
        // 筛选一级分类
        List<CategoryVO> categoryOneLevelList = list.stream()
                .filter(category -> category.getParentCid() == 0)
                .map(category -> BeanUtil.copyProperties(category, CategoryVO.class))
                .sorted(Comparator.comparingInt(CategoryVO::getSort))
                .collect(Collectors.toList());

        // 查找所有子元素
        categoryOneLevelList.forEach(category -> findChildren(category, list));

        return categoryOneLevelList;
    }

    /**
     * 批量删除菜单
     *
     * @param list 菜单ID集合
     */
    @Override
    @Transactional
    public void removeCategoryByIds(List<Long> list) {
        // TODO 判断分类是否被引用
        List<CategoryBrandRelationEntity> brandRelationEntities = categoryBrandRelationService.lambdaQuery().in(CategoryBrandRelationEntity::getCatelogId, list).list();

        if (!brandRelationEntities.isEmpty())
            // 存在关联关系
            throw new RuntimeException("存在关联关系");


        // TODO 判断菜单下是否有子菜单

        // 删除菜单
        boolean removed = removeByIds(list);

        if (!removed) {
            // 删除失败
            throw new RuntimeException("删除失败");
        }
    }

    /**
     * 保存菜单
     *
     * @param category 菜单数据
     */
    @Override
    public void saveCategory(CategoryEntity category) {
        // 数据校验
        if (StringUtils.isEmpty(category.getName()) && category.getParentCid() == null)
            throw new RuntimeException("菜单名称和父级菜单不能为空");

        // 获取父级菜单
        CategoryEntity categoryParentEntity = getById(category.getParentCid());
        // 添加一些默认数据
        category.setShowStatus(1);
        category.setSort(0);
        category.setCatLevel(categoryParentEntity.getCatLevel() + 1);
        category.setProductCount(0);
        boolean save = save(category);
        if (!save) {
            // 保存失败
            throw new RuntimeException("保存失败");
        }
    }

    @Override
    @Transactional
    public void updateCategory(CategoryEntity category) {
        // 无关自动置空 - 仿小人
        category.setShowStatus(null);
        category.setCatLevel(null);
        category.setSort(null);
        category.setParentCid(null);
        category.setProductCount(null);
        if (!this.updateById(category))
            throw new RuntimeException("更新失败");

        // TODO 更新关联表 - 分类名更新
        if (StrUtil.isNotEmpty(category.getName()))
            // 更新关联表 - 品牌名更新
            if (!categoryBrandRelationService.lambdaUpdate()
                    .eq(CategoryBrandRelationEntity::getCatelogId, category.getCatId())
                    .set(CategoryBrandRelationEntity::getCatelogName, category.getName())
                    .update()
            )
                throw new RuntimeException("分类名更新失败");

    }

    /**
     * 查询所有2,3分类
     *
     * @return 2, 3级分类
     */
    @Override
    public R getCategoryList() {

        String categoryList = stringRedisTemplate.opsForValue().get("CategoryList");
        if (StrUtil.isNotEmpty(categoryList)) {
            TypeReference<HashMap<String, List<CategoryTwoLevelVo>>> reference = new TypeReference<HashMap<String, List<CategoryTwoLevelVo>>>() {
            };
            try {
                HashMap<String, List<CategoryTwoLevelVo>> listHashMap = jsonMapper.readValue(categoryList, reference);
                return R.success(listHashMap);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        // 本地锁
//        return getCategoryListLocalLock();

        // redis锁 - 占位
        return getCategoryListFromRedisLock();

    }

    /**
     * 使用redisson锁 - 锁
     *
     * @return
     */
    private R getCategoryListFromRedissonLock() {

        RLock categoryLockRedisson = redissonClient.getLock("CategoryLockRedisson");

        categoryLockRedisson.lock();
        // 获取缓存 - 是否存在
        String categoryList = stringRedisTemplate.opsForValue().get("CategoryList");
        if (StrUtil.isNotEmpty(categoryList)) {
            TypeReference<HashMap<String, List<CategoryTwoLevelVo>>> reference = new TypeReference<HashMap<String, List<CategoryTwoLevelVo>>>() {
            };
            try {
                HashMap<String, List<CategoryTwoLevelVo>> listHashMap = jsonMapper.readValue(categoryList, reference);
                return R.success(listHashMap);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        // 获取锁成功
        R r = getCategoryListFromDB();  // 方法已设置缓存
        System.out.println("读取数据库成功 - 已设置缓存");
        categoryLockRedisson.unlock();

        return r;
    }

    /**
     * 使用redis锁 - 占位
     *
     * @return 结果
     */
    private R getCategoryListFromRedisLock() {
        while (true) {
            // 获取缓存 - 是否存在
            String categoryList = stringRedisTemplate.opsForValue().get("CategoryList");
            if (StrUtil.isNotEmpty(categoryList)) {
                TypeReference<HashMap<String, List<CategoryTwoLevelVo>>> reference = new TypeReference<HashMap<String, List<CategoryTwoLevelVo>>>() {
                };
                try {
                    HashMap<String, List<CategoryTwoLevelVo>> listHashMap = jsonMapper.readValue(categoryList, reference);
                    return R.success(listHashMap);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            // 使用 set key value NX 设置一个锁
            Boolean save = stringRedisTemplate.opsForValue().setIfAbsent("CategoryLock", "Lock");
            if (save != null && save) {
                // 获取锁成功
                R r = getCategoryListFromDB();  // 方法已设置缓存
                System.out.println("读取数据库成功 - 已设置缓存");
                // 释放锁
                stringRedisTemplate.delete("CategoryLock");
                return r;
            } else {
                try {
                    // 休眠 100ms
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * 本地锁
     * synchronized this对象锁 - bean在spring是单例的
     *
     * @return 2, 3级分类
     */
    private R getCategoryListLocalLock() {
        return getCategoryListFromDB();
    }

    private synchronized R getCategoryListFromDB() {
        String categoryList = stringRedisTemplate.opsForValue().get("CategoryList");
        if (StrUtil.isNotEmpty(categoryList)) {
            TypeReference<HashMap<String, List<CategoryTwoLevelVo>>> reference = new TypeReference<HashMap<String, List<CategoryTwoLevelVo>>>() {
            };
            try {
                HashMap<String, List<CategoryTwoLevelVo>> listHashMap = jsonMapper.readValue(categoryList, reference);
                return R.success(listHashMap);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        HashMap<String, List<CategoryTwoLevelVo>> category2LevelList = new HashMap<>();
        HashMap<String, List<CategoryThreeLevelVo>> category3LevelList = new HashMap<>();

        List<CategoryEntity> categoryEntities = this.lambdaQuery()
                .ne(CategoryEntity::getParentCid, 0)
                .list();
        categoryEntities = categoryEntities.stream()
                .filter(category -> {
                    // 筛选三级分类
                    if (category.getCatLevel() == 3) {
                        category3LevelList.computeIfAbsent(category.getParentCid().toString(), k -> new LinkedList<>());
                        CategoryThreeLevelVo threeLevelVo = BeanUtil.copyProperties(category, CategoryThreeLevelVo.class);
                        threeLevelVo.setCatalog2Id(category.getParentCid());
                        threeLevelVo.setId(category.getCatId());
                        category3LevelList.get(category.getParentCid().toString()).add(threeLevelVo);
                        return false;
                    }
                    return true;
                }).collect(Collectors.toList());

        long count = categoryEntities.stream()
                .filter(category -> {
                    // 筛选二级分类
                    if (category.getCatLevel() == 2) {
                        category2LevelList.computeIfAbsent(category.getParentCid().toString(), k -> new LinkedList<>());
                        CategoryTwoLevelVo twoLevelVo = BeanUtil.copyProperties(category, CategoryTwoLevelVo.class);
                        twoLevelVo.setCatalog1Id(category.getParentCid());
                        twoLevelVo.setId(category.getCatId());
                        twoLevelVo.setCatalog3List(category3LevelList.get(category.getCatId().toString()));
                        category2LevelList.get(category.getParentCid().toString()).add(twoLevelVo);
                        return false;
                    }
                    return true;
                })
                .count();

        if (count > 0) {
            throw new RuntimeException("stream 逻辑出错");
        }

        System.out.println("读取数据库成功 - 正在设置缓存");

        try {
            // 设置缓存
            stringRedisTemplate.opsForValue().set("CategoryList", jsonMapper.writeValueAsString(category2LevelList));
            return R.success(category2LevelList);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 查找所有子元素
     *
     * @param categoryVO 父级分类
     * @param list       所有分类
     */
    private void findChildren(CategoryVO categoryVO, List<CategoryEntity> list) {
        list.stream().filter(category -> Objects.equals(category.getParentCid(), categoryVO.getCatId()))
                .map(category -> BeanUtil.copyProperties(category, CategoryVO.class))
                .sorted(Comparator.comparingInt(CategoryVO::getSort))
                .forEach(category -> {
                    // 如果子元素集合为空，则创建一个
                    categoryVO.setChildren(categoryVO.getChildren() == null ? new java.util.ArrayList<>() : categoryVO.getChildren());
                    categoryVO.getChildren().add(category);
                    findChildren(category, list);
                });
    }

}